"""
CLI命令入口
使用Typer实现命令行接口
"""
import sys
from pathlib import Path
import typer
from rich.console import Console
from rich.table import Table
from loguru import logger

from storyforge.config import load_config, get_config
from storyforge.utils import (
    setup_logging, get_project_dir, save_json, load_json,
    generate_id, get_cost_report, copy_file, load_text
)
from storyforge.models import Project, StoryBible
from storyforge.services import (
    OutlineService, NovelService, CharacterService, ShotsService, TTSService,
    SubtitleService, ComposeService, get_job_manager
)

# 创建Typer应用和控制台
app = typer.Typer(help="StoryForgeAI - 自动化短剧生成系统")
console = Console()


@app.command()
def init(
    name: str = typer.Argument(..., help="项目名称")
):
    """初始化项目"""
    try:
        logger.info(f"初始化项目: {name}")
        config = load_config()
        project_dir = get_project_dir(name, config.paths.projects)
        
        # 创建项目元数据
        project = Project(
            id=generate_id("proj_"),
            name=name,
            status="draft"
        )
        
        project_file = project_dir / "project.json"
        save_json(project.dict(), str(project_file))
        
        logger.info(f"项目已创建: {project_dir}")
        logger.info(f"项目ID: {project.id}")
        sys.exit(0)
    except Exception as e:
        logger.error(f"创建项目失败: {e}")
        logger.exception("创建项目失败")
        sys.exit(1)


# 创建outline子命令组
outline_app = typer.Typer(help="大纲管理")
app.add_typer(outline_app, name="outline")

# 创建novel子命令组
novel_app = typer.Typer(help="小说正文生成")
app.add_typer(novel_app, name="novel")

# 创建character子命令组
character_app = typer.Typer(help="人物定型管理")
app.add_typer(character_app, name="character")

# 创建episodes子命令组
episodes_app = typer.Typer(help="分集生成")
app.add_typer(episodes_app, name="episodes")

@character_app.command("generate")
def character_generate(
    project: str = typer.Option(..., "--project", "-p", help="项目名称"),
    character: str = typer.Option(None, "--character", help="角色名称（可选，不指定则为所有主要角色生成）"),
    count: int = typer.Option(None, "--count", "-c", help="每个角色生成的参考图数量，默认 4"),
    provider: str = typer.Option(None, "--provider", help="图像生成提供商（可选）"),
    age: int = typer.Option(None, "--age", help="本次参考图对应的角色年龄阶段（可选，例如 18 表示 18 岁左右形象）"),
    episodes: str = typer.Option(None, "--episodes", help="本次参考图适用的剧集范围（可选，例如 '1-20'）"),
    time_label: str = typer.Option(None, "--time-label", help="时间/阶段标签（可选，例如 '学生时代'、'末日前'、'末日25年后'）"),
):
    """生成角色参考图（人物定型）
    
    这是保证人物一致性的必选步骤，必须在生成分集后、渲染镜头前完成。
    """
    try:
        config = load_config()
        service = CharacterService()
        
        console.print(f"[yellow]正在生成角色参考图...[/yellow]")
        if character:
            console.print(f"  角色: {character}")
        else:
            console.print(f"  角色: 所有主要角色")
        console.print(f"  数量: {count or config.generation.characterReferences.count} 张/角色")
        if age is not None:
            console.print(f"  年龄阶段: {age} 岁左右")
        if episodes:
            console.print(f"  剧集范围: {episodes}")
        if time_label:
            console.print(f"  时间标签: {time_label}")
        
        result = service.generate_references(
            project_name=project,
            character_name=character,
            count=count,
            provider_name=provider,
            age=age,
            episode_range=episodes,
            time_label=time_label,
        )
        
        console.print(f"\n[green]SUCCESS[/green] 角色参考图生成完成！")
        for char_name, refs in result.items():
            console.print(f"  {char_name}: {len(refs)} 张")
        
        console.print(f"\n[yellow]下一步[/yellow]: 审核参考图，使用 'storyforge character select' 锁定主参考图")
        sys.exit(0)
    except Exception as e:
        console.print(f"[red]ERROR[/red] 生成角色参考图失败: {e}")
        logger.exception("生成角色参考图失败")
        sys.exit(1)


@character_app.command("select")
def character_select(
    project: str = typer.Option(..., "--project", "-p", help="项目名称"),
    character: str = typer.Option(..., "--character", help="角色名称"),
    image: str = typer.Option(..., "--image", help="选中的参考图路径")
):
    """筛选锁定角色参考图
    
    人工审核后，锁定选中的参考图作为"视觉锚点"，用于后续所有镜头生成。
    """
    try:
        config = load_config()
        service = CharacterService()
        
        console.print(f"[yellow]正在锁定角色参考图...[/yellow]")
        console.print(f"  角色: {character}")
        console.print(f"  参考图: {image}")
        
        char_obj = service.select_reference(
            project_name=project,
            character_name=character,
            image_path=image
        )
        
        console.print(f"\n[green]SUCCESS[/green] 角色已锁定！")
        console.print(f"  主参考图: {char_obj.primaryReference}")
        console.print(f"  锁定时间: {char_obj.lockedAt}")
        
        console.print(f"\n[bold]提示[/bold]: 后续视频生成将采用\"图生视频\"模式，以参考图为基础生成镜头")
        sys.exit(0)
    except Exception as e:
        console.print(f"[red]ERROR[/red] 锁定参考图失败: {e}")
        logger.exception("锁定参考图失败")
        sys.exit(1)


@novel_app.command("generate")
def novel_generate(
    project: str = typer.Option(..., "--project", "-p", help="项目名称"),
    provider: str = typer.Option(None, "--provider", help="提供商名称（可选，单模型模式使用）"),
    chapters: int = typer.Option(None, "--chapters", "-c", help="期望章节数（可选）"),
    episodes: int = typer.Option(None, "--episodes", "-e", help="参考目标集数（可选）"),
    prompt_set: str = typer.Option(None, "--prompt-set", help="提示词集/题材（可选，不指定则自动识别。可选值: default, military, romance, fantasy）"),
    pipeline: str = typer.Option("single", "--pipeline", help="流水线模式：single|multi-role（默认 single）")
):
    """基于详细大纲生成小说正文（章节化）。"""
    try:
        config = load_config()
        outline_service = OutlineService()
        novel_service = NovelService()
        
        # 如果未指定题材，自动识别
        if prompt_set is None:
            console.print(f"[yellow]正在自动识别题材...[/yellow]")
            
            # 读取详细大纲或粗粒度大纲
            project_dir = get_project_dir(project, config.paths.projects)
            detailed_outline_path = project_dir / "detailed_outline.md"
            outline_path = project_dir / "outline.md"
            
            if detailed_outline_path.exists():
                outline_content = load_text(str(detailed_outline_path))
            elif outline_path.exists():
                outline_content = load_text(str(outline_path))
            else:
                raise FileNotFoundError("未找到大纲文件，请先运行 outline import")
            
            genre_info = outline_service.identify_genre(outline_content, provider)
            prompt_set = genre_info.get("genre", "default")
            
            genre_names = {
                "military": "军事题材",
                "romance": "言情题材",
                "fantasy": "玄幻题材",
                "default": "通用题材"
            }
            console.print(f"[green]√[/green] 识别为: [cyan]{genre_names.get(prompt_set, prompt_set)}[/cyan]")
        
        if pipeline == "multi-role":
            console.print(f"[yellow]正在使用多角色模式生成小说正文...[/yellow]")
        
        novel = novel_service.generate_novel(
            project_name=project,
            provider_name=provider,
            prompt_set=prompt_set,
            target_episodes=episodes,
            chapters=chapters,
            pipeline=pipeline
        )

        console.print(f"[green]SUCCESS[/green] 小说正文已生成：projects/{project}/novel.md")
        if "title" in novel:
            console.print(f"标题: {novel['title']}")
        sys.exit(0)
    except Exception as e:
        console.print(f"[red]ERROR[/red] 生成小说失败: {e}")
        logger.exception("生成小说失败")
        sys.exit(1)

@outline_app.command("generate")
def outline_generate(
    path: str = typer.Argument(..., help="粗粒度大纲文件路径"),
    project: str = typer.Option(..., "--project", "-p", help="项目名称"),
    episodes: int = typer.Option(None, "--episodes", "-e", help="目标集数（可选，不指定则由LLM自行决定）"),
    provider: str = typer.Option(None, "--provider", help="提供商名称（可选，单模型模式使用）"),
    prompt_set: str = typer.Option(None, "--prompt-set", help="提示词集/题材（可选，不指定则自动识别。可选值: default, military, romance, fantasy）"),
    pipeline: str = typer.Option("single", "--pipeline", help="流水线模式：single|multi-role（默认 single）")
):
    """
    生成详细大纲（基于粗粒度大纲文件）
    
    此命令将：
    1. 导入外部粗粒度大纲文件到项目目录
    2. 自动识别题材类型（如未指定 --prompt-set）
    3. 调用LLM生成详细大纲（包含世界观、人物设定、主线剧情、关键冲突等）
    4. 保存为 detailed_outline.json 和 detailed_outline.md
    
    参数说明：
    - --episodes: 可选参数。如不指定，LLM将根据大纲内容自行决定合适的集数（通常为80-120集）
    - --prompt-set: 可选参数。如不指定，系统将自动识别题材类型（military/romance/fantasy/default）
    - --pipeline: 可选参数。选择单模型（single）或多角色协作（multi-role）模式
    
    这是一个原子操作，无需手动执行多次。
    """
    try:
        config = load_config()
        service = OutlineService()
        
        # 步骤1: 导入粗粒度大纲
        console.print(f"[yellow]步骤 1/3:[/yellow] 导入粗粒度大纲...")
        outline_content = service.import_outline(project, path)
        
        console.print(f"[green]√[/green] 粗粒度大纲已导入")
        console.print(f"  项目: {project}")
        console.print(f"  文件: {path}")
        console.print(f"  长度: {len(outline_content)} 字符")
        
        # 步骤2: 自动识别题材（如果未指定）
        logger.info(f"prompt_set: {prompt_set}")
        logger.info(f"provider: {provider}")
        if prompt_set is None:
            logger.info(f"自动识别题材")
            console.print(f"\n[yellow]步骤 2/3:[/yellow] 自动识别题材...")
            genre_info = service.identify_genre(outline_content, provider)
            
            prompt_set = genre_info.get("genre", "default")
            confidence = genre_info.get("confidence", 0)
            reasoning = genre_info.get("reasoning", "")
            keywords = genre_info.get("keywords", [])
            
            logger.info(f"[green]√[/green] 题材识别完成")
            logger.info(f"  识别结果: [cyan]{prompt_set}[/cyan]")
            logger.info(f"  置信度: {confidence:.0%}")
            logger.info(f"  关键词: {', '.join(keywords)}")
            logger.info(f"  理由: {reasoning}")
            
            # 映射题材名称到中文
            genre_names = {
                "military": "军事题材",
                "romance": "言情题材",
                "fantasy": "玄幻题材",
                "default": "通用题材"
            }
            logger.info(f"  将使用: [bold cyan]{genre_names.get(prompt_set, prompt_set)}[/bold cyan] 提示词")
        else:
            logger.info(f"\n[yellow]步骤 2/3:[/yellow] 使用指定题材")
            logger.info(f"  题材: [cyan]{prompt_set}[/cyan]")
        
        # 步骤3: 自动生成详细大纲
        logger.info(f"\n[yellow]步骤 3/3:[/yellow] 生成详细大纲...")
        if episodes is None:
            logger.info(f"  集数: 由LLM根据大纲内容自行决定")
        else:
            logger.info(f"  目标集数: {episodes}")
        
        if pipeline == "multi-role":
            logger.info(f"  模式: 多角色协作")
        else:
            logger.info(f"  模式: 单模型")
            
        detailed_outline = service.generate_detailed_outline(
            project_name=project,
            target_episodes=episodes,
            provider_name=provider,
            prompt_set=prompt_set,
            pipeline=pipeline
        )
        
        logger.info(f"[green]√[/green] 详细大纲已生成")
        if "title" in detailed_outline:
            logger.info(f"  故事标题: {detailed_outline['title']}")
        if "targetEpisodes" in detailed_outline:
            logger.info(f"  建议集数: {detailed_outline['targetEpisodes']}")
        
        console.print(f"\n[green]SUCCESS[/green] 详细大纲生成完成！")
        
        # 根据实际集数给出提示
        suggested_episodes = detailed_outline.get('targetEpisodes', episodes or 100)
        logger.info(f"[yellow]下一步[/yellow]: 使用 'storyforge generate --project {project} --episodes {suggested_episodes}' 生成分集")
        
        sys.exit(0)
    except Exception as e:
        logger.error(f"[red]ERROR[/red] 处理大纲失败: {e}")
        logger.exception("处理大纲失败")
        sys.exit(1)


@episodes_app.command("generate")
def episodes_generate(
    project: str = typer.Option(..., "--project", "-p", help="项目名称"),
    count: int = typer.Option(None, "--count", "-c", help="剧集数量"),
    episodes: int = typer.Option(None, "--episodes", "-e", help="剧集数量（与--count等价，二选一）"),
    provider: str = typer.Option(None, "--provider", help="提供商名称（单模型模式）"),
    pipeline: str = typer.Option("single", "--pipeline", help="流水线模式：single|multi-role"),
    roles: str = typer.Option("default", "--roles", help="角色配置")
):
    """生成分集"""
    try:
        # 支持 --count 和 --episodes 两种参数名，至少需要指定一个
        episode_count = count if count is not None else episodes
        if episode_count is None:
            raise ValueError("必须指定 --count 或 --episodes 参数")
        
        config = load_config()
        service = OutlineService()
        
        # 自动加载大纲（优先使用详细大纲，如果没有则使用粗粒度大纲）
        if pipeline == "multi-role":
            console.print(f"[yellow]使用多角色模式生成 {episode_count} 集...[/yellow]")
            episode_list = service.generate_episodes_multi_role(project, None, episode_count)
        else:
            console.print(f"[yellow]使用单模型模式生成 {episode_count} 集...[/yellow]")
            episode_list = service.generate_episodes_single_model(
                project, None, episode_count, provider
            )
        
        console.print(f"[green]SUCCESS[/green] 成功生成 {len(episode_list)} 集")
        sys.exit(0)
    except Exception as e:
        console.print(f"[red]ERROR[/red] 生成分集失败: {e}")
        logger.exception("生成分集失败")
        sys.exit(1)


@app.command()
def shots(
    plan_cmd: str = typer.Argument("plan", help="命令：plan"),
    project: str = typer.Option(..., "--project", "-p", help="项目名称"),
    episode: int = typer.Option(..., "--episode", "-e", help="剧集序号"),
    template: str = typer.Option("default", "--template", "-t", help="模板名称")
):
    """规划分镜"""
    try:
        config = load_config()
        service = ShotsService()
        shots = service.plan_shots(project, episode, template)
        
        console.print(f"[green]SUCCESS[/green] 已规划 {len(shots)} 个镜头")
        sys.exit(0)
    except Exception as e:
        console.print(f"[red]ERROR[/red] 规划分镜失败: {e}")
        logger.exception("规划分镜失败")
        sys.exit(1)


@app.command()
def assets(
    request_cmd: str = typer.Option("request", help="命令：request|attach"),
    project: str = typer.Option(..., "--project", "-p", help="项目名称"),
    episode: int = typer.Option(None, "--episode", "-e", help="剧集序号"),
    provider: str = typer.Option(None, "--provider", help="提供商名称"),
    shot: str = typer.Option(None, "--shot", help="镜头标识（episode-shot）"),
    file: str = typer.Option(None, "--file", "-f", help="文件路径（attach模式）")
):
    """资产生成或绑定"""
    try:
        config = load_config()
        
        if request_cmd == "attach" and file:
            # 手工绑定资产
            if not shot:
                raise ValueError("attach模式需要指定--shot参数")
            
            episode_idx, shot_idx = shot.split('-')
            # 复制文件到资产目录
            assets_dir = Path(config.paths.assets) / project
            assets_dir.mkdir(parents=True, exist_ok=True)
            
            dst_file = assets_dir / f"ep{episode_idx}_shot{shot_idx}_{Path(file).name}"
            copy_file(file, str(dst_file))
            
            console.print(f"[green]SUCCESS[/green] 资产已绑定: {dst_file}")
        else:
            console.print("[yellow]资产生成功能待实现[/yellow]")
        
        sys.exit(0)
    except Exception as e:
        console.print(f"[red]ERROR[/red] 处理资产失败: {e}")
        logger.exception("处理资产失败")
        sys.exit(1)


@app.command()
def tts(
    synthesize_cmd: str = typer.Argument("synthesize", help="命令：synthesize"),
    project: str = typer.Option(..., "--project", "-p", help="项目名称"),
    episode: int = typer.Option(..., "--episode", "-e", help="剧集序号"),
    voice: str = typer.Option("narrator", "--voice", "-v", help="音色标识")
):
    """TTS合成"""
    try:
        config = load_config()
        service = TTSService()
        audio_path = service.synthesize(project, episode, voice)
        
        if audio_path:
            console.print(f"[green]SUCCESS[/green] TTS合成完成: {audio_path}")
        else:
            console.print("[yellow]WARNING[/yellow] 没有对白内容，跳过TTS")
        
        sys.exit(0)
    except Exception as e:
        console.print(f"[red]ERROR[/red] TTS合成失败: {e}")
        logger.exception("TTS合成失败")
        sys.exit(1)


@app.command()
def subtitle(
    align_cmd: str = typer.Argument("align", help="命令：align"),
    project: str = typer.Option(..., "--project", "-p", help="项目名称"),
    episode: int = typer.Option(..., "--episode", "-e", help="剧集序号"),
    audio: str = typer.Option(None, "--audio", help="音频文件路径"),
    out: str = typer.Option(None, "--out", help="输出文件路径")
):
    """字幕对齐"""
    try:
        config = load_config()
        service = SubtitleService()
        
        if not audio:
            audio = str(Path(config.paths.outputs) / f"ep{episode}.wav")
        
        subtitle_path = service.align(project, episode, audio, out)
        
        if subtitle_path:
            console.print(f"[green]SUCCESS[/green] 字幕已生成: {subtitle_path}")
        else:
            console.print("[yellow]WARNING[/yellow] 没有对白内容，跳过字幕")
        
        sys.exit(0)
    except Exception as e:
        console.print(f"[red]ERROR[/red] 字幕对齐失败: {e}")
        logger.exception("字幕对齐失败")
        sys.exit(1)


# 创建render子命令组
render_app = typer.Typer(help="渲染出片")
app.add_typer(render_app, name="render")

@render_app.command("episode")
def render_episode(
    index: int = typer.Argument(..., help="剧集序号"),
    project: str = typer.Option(..., "--project", "-p", help="项目名称"),
    out: str = typer.Option(None, "--out", help="输出文件路径")
):
    """渲染单集视频
    
    前置检查：系统会检查该集涉及的所有主要角色是否已完成人物定型（locked=true），
    未完成则报错并提示先完成人物定型流程。
    """
    try:
        config = load_config()
        
        # 前置检查：验证人物定型是否完成
        console.print(f"[yellow]正在检查人物定型状态...[/yellow]")
        char_service = CharacterService()
        
        # 读取剧集数据，获取涉及的角色
        project_dir = get_project_dir(project, config.paths.projects)
        episode_file = project_dir / f"episode_{index}.json"
        
        if not episode_file.exists():
            raise FileNotFoundError(f"剧集文件不存在: {episode_file}")
        
        episode_data = load_json(str(episode_file))
        
        # 提取剧集中涉及的角色
        characters = set()
        script_json = episode_data.get("scriptJSON", {})
        scenes = script_json.get("scenes", [])
        for scene in scenes:
            characters.update(scene.get("characters", []))
        
        if characters:
            # 检查角色是否已锁定
            unlocked = char_service.get_unlocked_characters(project, list(characters))
            
            if unlocked:
                console.print(f"\n[red]ERROR[/red] 以下角色未完成人物定型（未锁定）:")
                for char_name in unlocked:
                    console.print(f"  - {char_name}")
                console.print(f"\n[yellow]请先完成人物定型流程:[/yellow]")
                console.print(f"  1. storyforge character generate --project {project}")
                console.print(f"  2. 人工审核参考图")
                console.print(f"  3. storyforge character select --project {project} --character <角色名> --image <参考图路径>")
                sys.exit(1)
            
            console.print(f"[green]√[/green] 所有角色已完成人物定型")
        
        # 渲染视频
        service = ComposeService()
        video_path = service.compose_episode(project, index, out)
        console.print(f"[green]SUCCESS[/green] 视频渲染完成: {video_path}")
        sys.exit(0)
    except Exception as e:
        console.print(f"[red]ERROR[/red] 视频渲染失败: {e}")
        logger.exception("视频渲染失败")
        sys.exit(1)

@render_app.command("season")
def render_season(
    range_str: str = typer.Argument(..., help="剧集范围，如：1-100"),
    project: str = typer.Option(..., "--project", "-p", help="项目名称"),
    out_dir: str = typer.Option(None, "--out", help="输出目录")
):
    """批量渲染剧集
    
    前置检查：系统会检查范围内所有集涉及的主要角色是否已完成人物定型，
    未完成则报错并列出所有未锁定的角色。
    """
    try:
        config = load_config()
        
        # 解析范围
        if "-" in range_str:
            start, end = range_str.split("-")
            start_idx = int(start)
            end_idx = int(end)
        else:
            start_idx = end_idx = int(range_str)
        
        # 前置检查：收集所有涉及的角色
        console.print(f"[yellow]正在检查人物定型状态...[/yellow]")
        char_service = CharacterService()
        project_dir = get_project_dir(project, config.paths.projects)
        
        all_characters = set()
        for idx in range(start_idx, end_idx + 1):
            episode_file = project_dir / f"episode_{idx}.json"
            if episode_file.exists():
                episode_data = load_json(str(episode_file))
                script_json = episode_data.get("scriptJSON", {})
                scenes = script_json.get("scenes", [])
                for scene in scenes:
                    all_characters.update(scene.get("characters", []))
        
        if all_characters:
            # 检查角色是否已锁定
            unlocked = char_service.get_unlocked_characters(project, list(all_characters))
            
            if unlocked:
                console.print(f"\n[red]ERROR[/red] 以下角色未完成人物定型（未锁定）:")
                for char_name in unlocked:
                    console.print(f"  - {char_name}")
                console.print(f"\n[yellow]请先完成人物定型流程:[/yellow]")
                console.print(f"  1. storyforge character generate --project {project}")
                console.print(f"  2. 人工审核参考图")
                console.print(f"  3. storyforge character select --project {project} --character <角色名> --image <参考图路径>")
                sys.exit(1)
            
            console.print(f"[green]√[/green] 所有角色已完成人物定型")
        
        # 批量渲染
        service = ComposeService()
        success_count = 0
        failed_count = 0
        
        for idx in range(start_idx, end_idx + 1):
            try:
                console.print(f"[yellow]渲染第 {idx} 集...[/yellow]")
                video_path = service.compose_episode(project, idx, None)
                console.print(f"[green]SUCCESS[/green] 第 {idx} 集完成: {video_path}")
                success_count += 1
            except Exception as e:
                console.print(f"[red]ERROR[/red] 第 {idx} 集失败: {e}")
                logger.exception(f"渲染第 {idx} 集失败")
                failed_count += 1
        
        console.print(f"\n[bold]渲染完成: 成功 {success_count} 集，失败 {failed_count} 集[/bold]")
        sys.exit(0 if failed_count == 0 else 1)
    except Exception as e:
        console.print(f"[red]ERROR[/red] 批量渲染失败: {e}")
        logger.exception("批量渲染失败")
        sys.exit(1)


@app.command()
def status(
    job_id: str = typer.Argument(..., help="任务ID")
):
    """查询任务状态"""
    try:
        config = load_config()
        job_manager = get_job_manager(config.paths.logs)
        job = job_manager.get_job(job_id)
        
        if job:
            table = Table(title=f"任务 {job_id}")
            table.add_column("字段")
            table.add_column("值")
            
            table.add_row("类型", job.type)
            table.add_row("状态", job.status)
            table.add_row("成本", f"${job.cost:.4f}")
            if job.error:
                table.add_row("错误", job.error)
            
            console.print(table)
        else:
            console.print(f"[red]ERROR[/red] 任务不存在: {job_id}")
            sys.exit(1)
        
        sys.exit(0)
    except Exception as e:
        console.print(f"[red]ERROR[/red] 查询任务失败: {e}")
        logger.exception("查询任务失败")
        sys.exit(1)


@app.command()
def cost(
    project: str = typer.Option(None, "--project", "-p", help="项目名称（可选）")
):
    """成本统计报告"""
    try:
        config = load_config()
        report = get_cost_report(project, config.paths.logs)
        
        table = Table(title=f"成本报告{' - ' + project if project else ''}")
        table.add_column("类型")
        table.add_column("金额")
        
        total = report.get("total", 0)
        by_type = report.get("by_type", {})
        by_provider = report.get("by_provider", {})
        
        # 按类型汇总
        if by_type:
            for cost_type, amount in by_type.items():
                table.add_row(f"[cyan]{cost_type}[/cyan]", f"${amount:.4f}")
        
        # 按提供商汇总
        if by_provider:
            table.add_row("", "", style="dim")  # 分隔线
            for provider, amount in by_provider.items():
                table.add_row(f"[yellow]{provider}[/yellow]", f"${amount:.4f}")
        
        table.add_row("", "", style="dim")  # 分隔线
        table.add_row("[bold]总计[/bold]", f"[bold]${total:.4f}[/bold]")
        console.print(table)
        
        sys.exit(0)
    except Exception as e:
        console.print(f"[red]ERROR[/red] 生成成本报告失败: {e}")
        logger.exception("生成成本报告失败")
        sys.exit(1)


def main():
    """主入口"""
    # 初始化日志
    config = load_config()
    setup_logging(config.paths.logs)
    
    # 运行CLI
    app()


if __name__ == "__main__":
    main()

